import { Directive, App } from "vue";

/**
 * 点击外部元素触发回调函数
 */

interface ClickOutsideData {
  el: Element;
  callback: () => void;
  handler?(e: MouseEvent): void;
}

// 判断第二个参数是否是第一个参数的子节点
function judgeChild(parent: Element, child: Element): boolean {
  if (parent.children.length === 0) return false;

  return Array.from(parent.children).some(
    (_child) => _child === child || judgeChild(_child, child)
  );
}

// 数据列表
const clickOutsideDataList: ClickOutsideData[] = [];

// document 点击事件
function handleDocumentClick(this: ClickOutsideData, e: MouseEvent) {
  const target = e.target as HTMLElement;
  if (this.el !== target && !judgeChild(this.el, target)) {
    this.callback && typeof this.callback === "function" && this.callback();
  }
}

export const directive: Directive<HTMLElement, () => void> = {
  mounted(el, binding) {
    const callback = binding.value;

    if (!callback || typeof callback !== "function") {
      throw new Error("v-click-outside 需要赋值 Function 函数");
    }

    // 设置数据
    const clickOutsideData: ClickOutsideData = { el, callback };
    clickOutsideData.handler = handleDocumentClick.bind(clickOutsideData);

    // 绑定事件
    document.addEventListener("click", clickOutsideData.handler);
    // 加入数据列表
    clickOutsideDataList.push(clickOutsideData);
  },

  unmounted(el) {
    const findIndex = clickOutsideDataList.findIndex((item) => item.el === el);
    const findObj = clickOutsideDataList[findIndex];

    // 解绑事件
    if (findObj && findObj.handler) {
      document.removeEventListener("click", findObj.handler);
    }
    // 移出数据列表
    clickOutsideDataList.splice(findIndex, 1);
  },
};

export const name = "click-outside";

export default {
  install(app: App) {
    app.directive(name, directive);
  },
};
